﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace FunctionGraph
{
    class Parser
    {
        private IEnumerable<string> LexicalParse(string text)
        {
            int pos = 0;
            int state = 0;
            StringBuilder tokenBuilder = new StringBuilder();
            while (pos < text.Length)
            {
                char curChar = text[pos];
                char nxtChar = '\0';
                if (pos < text.Length-1)
                    nxtChar = text[pos+1];
                switch (state)
                {
                    case 0:
                        if (char.IsDigit(curChar))
                            state = 1;
                        else if (char.IsLetter(curChar))
                            state = 2;
                        else if (curChar == '+' || curChar == '-' || curChar == '*'
                            || curChar == '/' || curChar == '%' || curChar == '(' || curChar == ')' || curChar=='<' || curChar=='=' || curChar=='>' || curChar=='&' || curChar=='|' || curChar=='^' || curChar=='!')
                            state = 3;
                        else
                            pos++;
                        break;
                    case 1:
                        if (char.IsDigit(curChar) || curChar == '.')
                        {
                            tokenBuilder.Append(curChar);
                            pos++;
                        }
                        else
                        {
                            yield return tokenBuilder.ToString();
                            tokenBuilder.Clear();
                            state = 0;
                        }
                        break;
                    case 2:
                        if (char.IsLetter(curChar) || curChar == '_')
                        {
                            tokenBuilder.Append(curChar);
                            pos++;
                        }
                        else
                        {
                            yield return tokenBuilder.ToString();
                            tokenBuilder.Clear();
                            state = 0;
                        }
                        break;
                    case 3:
                        if ((curChar == '>' || curChar == '<' || curChar=='!') && nxtChar == '=')
                        {
                            pos++;
                            yield return (curChar.ToString() + nxtChar);
                        }
                        else
                            yield return curChar.ToString();
                        state = 0;
                        pos++;
                        break;
                }
            }
            if (tokenBuilder.Length>0)
                yield return tokenBuilder.ToString();
        }

        string ReadWord(string[] words, ref int pos)
        {
            if (pos < words.Length)
                return words[pos++];
            else
                throw new InvalidOperationException();
        }

        double ReadConst(string[] words, ref int pos)
        {
            return double.Parse(ReadWord(words, ref pos));
        }

        public ExpressionNode Parse(string text)
        {
            try
            {
                var words = LexicalParse(text).ToArray();
                int pos = 0;
                return ParseExpr(words, ref pos);
            }
            catch (Exception)
            {
                return null;
            }
        }

        private ExpressionNode ParseExpr(string[] words, ref int pos)
        {
            return ParseBinaryExpr(words, 0, ref pos);
        }

        private static string[][] operators = new string[][]
        {
            new string[]{"|"},
            new string[]{"&"},
            new string[]{"=", ">", ">=", "<=", "<", "!=" },
            new string[]{"+", "-"},
            new string[]{"*", "/"},
            new string[]{"^"}
        };

        private static bool IsOperator(string op, int level)
        {
            foreach (var str in operators[level])
                if (str == op)
                    return true;
            return false;
        }

        private ExpressionNode ParseBinaryExpr(string[] words, int level, ref int pos)
        {
            ExpressionNode left;
            if (level == operators.Length - 1)
                left = ParseUnaryExpr(words, ref pos);
            else
                left = ParseBinaryExpr(words, level + 1, ref pos);
            do
            {
                if (pos >= words.Length)
                    return left;
                var op = words[pos];
                if (IsOperator(op, level))
                {
                    pos++;
                    BinaryNode rs = new BinaryNode();
                    rs.Operator = ParseOperator(op);
                    rs.Left = left;
                    if (level == operators.Length - 1)
                        rs.Right = ParseUnaryExpr(words, ref pos);
                    else
                        rs.Right = ParseBinaryExpr(words, level+1, ref pos);
                    left = rs;
                }
                else
                    return left;
            } while (true);
        }

        private MathOperator ParseOperator(string op)
        {
            switch (op)
            {
                case "|":
                    return MathOperator.Or;
                case "&":
                    return MathOperator.And;
                case "=":
                    return MathOperator.Eql;
                case "<=":
                    return MathOperator.Leq;
                case ">=":
                    return MathOperator.Geq;
                case "<":
                    return MathOperator.Less;
                case ">":
                    return MathOperator.Greater;
                case "!=":
                    return MathOperator.Neq;
                case "+":
                    return MathOperator.Add;
                case "-":
                    return MathOperator.Sub;
                case "*":
                    return MathOperator.Mul;
                case "/":
                    return MathOperator.Div;
                case "^":
                    return MathOperator.Pow;
            }
            return MathOperator.Illegal;
        }

        private MathOperator ParseUnaryOperator(string op)
        {
            switch(op)
            {
                case "-":
                    return MathOperator.Neg;
                case "sin":
                    return MathOperator.Sin;
                case "cos":
                    return MathOperator.Cos;
                case "abs":
                    return MathOperator.Abs;
                case "floor":
                    return MathOperator.Floor;
                case "ceil":
                case "ceiling":
                    return MathOperator.Ceil;

                default:
                    throw new InvalidExpressionException("unkown function: " + op);
            }
            
        }

        private ExpressionNode ParseUnaryExpr(string[] words, ref int pos)
        {
            if (words[pos] == "(")
            {
                pos++;
                var node = ParseExpr(words, ref pos);
                if (ReadWord(words, ref pos) != ")")
                    throw new InvalidOperationException();
                return node;
            }
            else if (words[pos] == "x" || words[pos] == "y")
            {
                var node = new VariableNode();
                node.VariableName = ReadWord(words, ref pos);
                return node;
            }
            else if (char.IsLetter(words[pos][0]) || words[pos] == "-")
            {
                var node = new UnaryNode();
                node.Operator = ParseUnaryOperator(ReadWord(words, ref pos));
                node.Child = ParseUnaryExpr(words, ref pos);
                return node;
            }
            else
            {
                var node = new ConstantNode();
                node.Value = ReadConst(words, ref pos);
                return node;
            }
        }

        
    }
}
